<?php

/**
 * Simple registration request and response parsing and object
 * representation.
 *
 * This module contains objects representing simple registration
 * requests and responses that can be used with both OpenID relying
 * parties and OpenID providers.
 *
 * 1. The relying party creates a request object and adds it to the
 * {@link Auth_OpenID_AuthRequest} object before making the
 * checkid request to the OpenID provider:
 *
 *   $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
 *   $auth_request->addExtension($sreg_req);
 *
 * 2. The OpenID provider extracts the simple registration request
 * from the OpenID request using {@link
 * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
 * approval and data, creates an {@link Auth_OpenID_SRegResponse}
 * object and adds it to the id_res response:
 *
 *   $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
 *								  $checkid_request);
 *   // [ get the user's approval and data, informing the user that
 *   //   the fields in sreg_response were requested ]
 *   $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
 *								  $sreg_req, $user_data);
 *   $sreg_resp->toMessage($openid_response->fields);
 *
 * 3. The relying party uses {@link
 * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
 * from the OpenID response:
 *
 *   $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
 *								  $success_response);
 *
 * @package OpenID
 */

// Do not allow direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

/**
 * Import message and extension internals.
 */
require_once 'Auth/OpenID/Message.php';
require_once 'Auth/OpenID/Extension.php';

// The data fields that are listed in the sreg spec
global $Auth_OpenID_sreg_data_fields;
$Auth_OpenID_sreg_data_fields = array(
									  'fullname' => 'Full Name',
									  'nickname' => 'Nickname',
									  'dob' => 'Date of Birth',
									  'email' => 'E-mail Address',
									  'gender' => 'Gender',
									  'postcode' => 'Postal Code',
									  'country' => 'Country',
									  'language' => 'Language',
									  'timezone' => 'Time Zone');

/**
 * Check to see that the given value is a valid simple registration
 * data field name.  Return true if so, false if not.
 */
function Auth_OpenID_checkFieldName($field_name)
{
	global $Auth_OpenID_sreg_data_fields;

	if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
		return false;
	}
	return true;
}

// URI used in the wild for Yadis documents advertising simple
// registration support
define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');

// URI in the draft specification for simple registration 1.1
// <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');

// This attribute will always hold the preferred URI to use when
// adding sreg support to an XRDS file or in an OpenID namespace
// declaration.
define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);

Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');

/**
 * Does the given endpoint advertise support for simple
 * registration?
 *
 * $endpoint: The endpoint object as returned by OpenID discovery.
 * returns whether an sreg type was advertised by the endpoint
 */
function Auth_OpenID_supportsSReg(&$endpoint)
{
	return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
			$endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
}

/**
 * A base class for classes dealing with Simple Registration protocol
 * messages.
 *
 * @package OpenID
 */
class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
	/**
	 * Extract the simple registration namespace URI from the given
	 * OpenID message. Handles OpenID 1 and 2, as well as both sreg
	 * namespace URIs found in the wild, as well as missing namespace
	 * definitions (for OpenID 1)
	 *
	 * $message: The OpenID message from which to parse simple
	 * registration fields. This may be a request or response message.
	 *
	 * Returns the sreg namespace URI for the supplied message. The
	 * message may be modified to define a simple registration
	 * namespace.
	 *
	 * @access private
	 */
	function _getSRegNS(&$message)
	{
		$alias = null;
		$found_ns_uri = null;

		// See if there exists an alias for one of the two defined
		// simple registration types.
		foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
					   Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
			$alias = $message->namespaces->getAlias($sreg_ns_uri);
			if ($alias !== null) {
				$found_ns_uri = $sreg_ns_uri;
				break;
			}
		}

		if ($alias === null) {
			// There is no alias for either of the types, so try to
			// add one. We default to using the modern value (1.1)
			$found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
			if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
											   'sreg') === null) {
				// An alias for the string 'sreg' already exists, but
				// it's defined for something other than simple
				// registration
				return null;
			}
		}

		return $found_ns_uri;
	}
}

/**
 * An object to hold the state of a simple registration request.
 *
 * required: A list of the required fields in this simple registration
 * request
 *
 * optional: A list of the optional fields in this simple registration
 * request
 *
 * @package OpenID
 */
class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {

	var $ns_alias = 'sreg';

	/**
	 * Initialize an empty simple registration request.
	 */
	function build($required=null, $optional=null,
				   $policy_url=null,
				   $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
				   $cls='Auth_OpenID_SRegRequest')
	{
		$obj = new $cls();

		$obj->required = array();
		$obj->optional = array();
		$obj->policy_url = $policy_url;
		$obj->ns_uri = $sreg_ns_uri;

		if ($required) {
			if (!$obj->requestFields($required, true, true)) {
				return null;
			}
		}

		if ($optional) {
			if (!$obj->requestFields($optional, false, true)) {
				return null;
			}
		}

		return $obj;
	}

	/**
	 * Create a simple registration request that contains the fields
	 * that were requested in the OpenID request with the given
	 * arguments
	 *
	 * $request: The OpenID authentication request from which to
	 * extract an sreg request.
	 *
	 * $cls: name of class to use when creating sreg request object.
	 * Used for testing.
	 *
	 * Returns the newly created simple registration request
	 */
	function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
	{

		$obj = call_user_func_array(array($cls, 'build'),
				 array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));

		// Since we're going to mess with namespace URI mapping, don't
		// mutate the object that was passed in.
		$m = $request->message;

		$obj->ns_uri = $obj->_getSRegNS($m);
		$args = $m->getArgs($obj->ns_uri);

		if ($args === null || Auth_OpenID::isFailure($args)) {
			return null;
		}

		$obj->parseExtensionArgs($args);

		return $obj;
	}

	/**
	 * Parse the unqualified simple registration request parameters
	 * and add them to this object.
	 *
	 * This method is essentially the inverse of
	 * getExtensionArgs. This method restores the serialized simple
	 * registration request fields.
	 *
	 * If you are extracting arguments from a standard OpenID
	 * checkid_* request, you probably want to use fromOpenIDRequest,
	 * which will extract the sreg namespace and arguments from the
	 * OpenID request. This method is intended for cases where the
	 * OpenID server needs more control over how the arguments are
	 * parsed than that method provides.
	 *
	 * $args == $message->getArgs($ns_uri);
	 * $request->parseExtensionArgs($args);
	 *
	 * $args: The unqualified simple registration arguments
	 *
	 * strict: Whether requests with fields that are not defined in
	 * the simple registration specification should be tolerated (and
	 * ignored)
	 */
	function parseExtensionArgs($args, $strict=false)
	{
		foreach (array('required', 'optional') as $list_name) {
			$required = ($list_name == 'required');
			$items = Auth_OpenID::arrayGet($args, $list_name);
			if ($items) {
				foreach (explode(',', $items) as $field_name) {
					if (!$this->requestField($field_name, $required, $strict)) {
						if ($strict) {
							return false;
						}
					}
				}
			}
		}

		$this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');

		return true;
	}

	/**
	 * A list of all of the simple registration fields that were
	 * requested, whether they were required or optional.
	 */
	function allRequestedFields()
	{
		return array_merge($this->required, $this->optional);
	}

	/**
	 * Have any simple registration fields been requested?
	 */
	function wereFieldsRequested()
	{
		return count($this->allRequestedFields());
	}

	/**
	 * Was this field in the request?
	 */
	function contains($field_name)
	{
		return (in_array($field_name, $this->required) ||
				in_array($field_name, $this->optional));
	}

	/**
	 * Request the specified field from the OpenID user
	 *
	 * $field_name: the unqualified simple registration field name
	 *
	 * required: whether the given field should be presented to the
	 * user as being a required to successfully complete the request
	 *
	 * strict: whether to raise an exception when a field is added to
	 * a request more than once
	 */
	function requestField($field_name,
						  $required=false, $strict=false)
	{
		if (!Auth_OpenID_checkFieldName($field_name)) {
			return false;
		}

		if ($strict) {
			if ($this->contains($field_name)) {
				return false;
			}
		} else {
			if (in_array($field_name, $this->required)) {
				return true;
			}

			if (in_array($field_name, $this->optional)) {
				if ($required) {
					unset($this->optional[array_search($field_name,
													   $this->optional)]);
				} else {
					return true;
				}
			}
		}

		if ($required) {
			$this->required[] = $field_name;
		} else {
			$this->optional[] = $field_name;
		}

		return true;
	}

	/**
	 * Add the given list of fields to the request
	 *
	 * field_names: The simple registration data fields to request
	 *
	 * required: Whether these values should be presented to the user
	 * as required
	 *
	 * strict: whether to raise an exception when a field is added to
	 * a request more than once
	 */
	function requestFields($field_names, $required=false, $strict=false)
	{
		if (!is_array($field_names)) {
			return false;
		}

		foreach ($field_names as $field_name) {
			if (!$this->requestField($field_name, $required, $strict=$strict)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Get a dictionary of unqualified simple registration arguments
	 * representing this request.
	 *
	 * This method is essentially the inverse of
	 * C{L{parseExtensionArgs}}. This method serializes the simple
	 * registration request fields.
	 */
	function getExtensionArgs()
	{
		$args = array();

		if ($this->required) {
			$args['required'] = implode(',', $this->required);
		}

		if ($this->optional) {
			$args['optional'] = implode(',', $this->optional);
		}

		if ($this->policy_url) {
			$args['policy_url'] = $this->policy_url;
		}

		return $args;
	}
}

/**
 * Represents the data returned in a simple registration response
 * inside of an OpenID C{id_res} response. This object will be created
 * by the OpenID server, added to the C{id_res} response object, and
 * then extracted from the C{id_res} message by the Consumer.
 *
 * @package OpenID
 */
class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {

	var $ns_alias = 'sreg';

	function Auth_OpenID_SRegResponse($data=null,
									  $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
	{
		if ($data === null) {
			$this->data = array();
		} else {
			$this->data = $data;
		}

		$this->ns_uri = $sreg_ns_uri;
	}

	/**
	 * Take a C{L{SRegRequest}} and a dictionary of simple
	 * registration values and create a C{L{SRegResponse}} object
	 * containing that data.
	 *
	 * request: The simple registration request object
	 *
	 * data: The simple registration data for this response, as a
	 * dictionary from unqualified simple registration field name to
	 * string (unicode) value. For instance, the nickname should be
	 * stored under the key 'nickname'.
	 */
	function extractResponse($request, $data)
	{
		$obj = new Auth_OpenID_SRegResponse();
		$obj->ns_uri = $request->ns_uri;

		foreach ($request->allRequestedFields() as $field) {
			$value = Auth_OpenID::arrayGet($data, $field);
			if ($value !== null) {
				$obj->data[$field] = $value;
			}
		}

		return $obj;
	}

	/**
	 * Create a C{L{SRegResponse}} object from a successful OpenID
	 * library response
	 * (C{L{openid.consumer.consumer.SuccessResponse}}) response
	 * message
	 *
	 * success_response: A SuccessResponse from consumer.complete()
	 *
	 * signed_only: Whether to process only data that was
	 * signed in the id_res message from the server.
	 *
	 * Returns a simple registration response containing the data that
	 * was supplied with the C{id_res} response.
	 */
	function fromSuccessResponse(&$success_response, $signed_only=true)
	{
		global $Auth_OpenID_sreg_data_fields;

		$obj = new Auth_OpenID_SRegResponse();
		$obj->ns_uri = $obj->_getSRegNS($success_response->message);

		if ($signed_only) {
			$args = $success_response->getSignedNS($obj->ns_uri);
		} else {
			$args = $success_response->message->getArgs($obj->ns_uri);
		}

		if ($args === null || Auth_OpenID::isFailure($args)) {
			return null;
		}

		foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
			if (in_array($field_name, array_keys($args))) {
				$obj->data[$field_name] = $args[$field_name];
			}
		}

		return $obj;
	}

	function getExtensionArgs()
	{
		return $this->data;
	}

	// Read-only dictionary interface
	function get($field_name, $default=null)
	{
		if (!Auth_OpenID_checkFieldName($field_name)) {
			return null;
		}

		return Auth_OpenID::arrayGet($this->data, $field_name, $default);
	}

	function contents()
	{
		return $this->data;
	}
}

?>
